#pragma once
#include <string>
#include <functional>
#include <SDL2/SDL.h>

class Window {
public:
    Window(const std::string &title = "Window", int w = 800, int h = 600, int x = SDL_WINDOWPOS_CENTERED,
                int y = SDL_WINDOWPOS_CENTERED)
        : on_process_event(default_on_process_event), on_render(default_on_render), on_resize(default_on_resize),
          on_exec(default_on_exec) {
        SDL_Init(SDL_INIT_EVERYTHING);
        SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
        SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
        SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
        sdl_window = SDL_CreateWindow(title.c_str(), x, y, w, h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
        gl_context = SDL_GL_CreateContext(sdl_window);
        SDL_GL_SetSwapInterval(-1);
        SDL_GL_MakeCurrent(sdl_window, gl_context);
    }

    ~Window() {
        SDL_GL_DeleteContext(gl_context);
        SDL_DestroyWindow(sdl_window);
        SDL_Quit();
    }

public:
    struct vec2i {
        int x, y;
    };
    vec2i get_size() const {
        int w, h;
        SDL_GetWindowSize(sdl_window, &w, &h);
        return {w, h};
    }
    void set_size(int w, int h) { SDL_SetWindowSize(sdl_window, w, h); }
    void set_title(const char *title) { SDL_SetWindowTitle(sdl_window, title); }
    void set_title(const std::string &title) { SDL_SetWindowTitle(sdl_window, title.c_str()); }
    void exit(int code) {
        is_running = 0;
        ret_code = code;
    }
    int exec() {
        on_exec(this);
        while (is_running) {
            while (SDL_PollEvent(&sdl_event)) {
                if (sdl_event.type == SDL_QUIT) {
                    exit(0);
                } else if (sdl_event.type == SDL_WINDOWEVENT && sdl_event.window.event == SDL_WINDOWEVENT_RESIZED) {
                    on_resize(this, sdl_event.window.data1, sdl_event.window.data2);
                } else {
                    on_process_event(this, &sdl_event);
                }
            }
            on_render(this);
            SDL_GL_SwapWindow(sdl_window);
        }
        return ret_code;
    }

public:
    std::function<void(Window *)> on_exec;
    std::function<void(Window *)> on_render;
    std::function<void(Window *, int, int)> on_resize;
    std::function<void(Window *, const SDL_Event *)> on_process_event;

private:
    static void default_on_process_event(Window *window, const SDL_Event *event) {}
    static void default_on_exec(Window *window) {}
    static void default_on_render(Window *) {}
    static void default_on_resize(Window *, int, int) {}

private:
    int is_running = 1;
    int ret_code = 0;
    SDL_Window *sdl_window = nullptr;
    SDL_GLContext gl_context = nullptr;
    SDL_Event sdl_event{};
};